<template>
  <div ref="boxRef" class="star-box">
    <canvas
      ref="canvasRef"
      :style="{ background: bgColor }"
      class="star-canvas"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';

interface Star {
  x: number;
  y: number;
  r: number;
  opacity: number;
  speed: number;
  phase: number;
}

/* ---------- props ---------- */
const props = withDefaults(
  defineProps<{
    starCount?: number;
    starSize?: number;
    twinkleSpeed?: number;
    bgColor?: string;
  }>(),
  {
    starCount: 800,
    starSize: 2,
    twinkleSpeed: 5,
    bgColor: 'transparent',
  }
);

/* ---------- dom & canvas ---------- */
const boxRef = ref<HTMLDivElement>();
const canvasRef = ref<HTMLCanvasElement>();
let ctx: CanvasRenderingContext2D;
let stars: Star[] = [];
let raf = 0;
let ro: ResizeObserver;

/* ---------- 逻辑 ---------- */
function resize() {
  const box = boxRef.value!;
  const c = canvasRef.value!;
  const rect = box.getBoundingClientRect();
  c.width = rect.width;
  c.height = rect.height;
}

function createStars() {
  const c = canvasRef.value!;
  const w = c.width;
  const h = c.height;
  stars = [];
  for (let i = 0; i < props.starCount; i++) {
    stars.push({
      x: Math.random() * w,
      y: Math.random() * h,
      r: Math.random() * props.starSize + 0.5,
      opacity: Math.random(),
      speed: Math.random() * 0.02 + 0.01,
      phase: Math.random() * Math.PI * 2,
    });
  }
}

function draw() {
  const c = canvasRef.value!;
  ctx.clearRect(0, 0, c.width, c.height);
  ctx.fillStyle = '#fff';
  stars.forEach((s) => {
    s.phase += s.speed * props.twinkleSpeed;
    const alpha = s.opacity * (Math.sin(s.phase) * 0.5 + 0.5);
    ctx.globalAlpha = alpha;
    ctx.beginPath();
    ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
    ctx.fill();
  });
  ctx.globalAlpha = 1;
}

function animate() {
  draw();
  raf = requestAnimationFrame(animate);
}

/* ---------- 生命周期 ---------- */
onMounted(() => {
  ctx = canvasRef.value!.getContext('2d')!;
  resize();
  createStars();
  animate();

  // 监听父容器大小变化
  ro = new ResizeObserver(() => {
    resize();
    createStars();
  });
  ro.observe(boxRef.value!);
});

onUnmounted(() => {
  cancelAnimationFrame(raf);
  ro?.disconnect();
});

/* 动态改参 */
watch(() => [props.starCount, props.starSize], createStars);
</script>

<style scoped>
.star-box {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.star-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
</style>
